package vip.liux.front.infrastructure.aop.interceptor;

import com.google.common.base.Stopwatch;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
import vip.liux.core.current.ICurrentUser;
import vip.liux.front.infrastructure.utils.AmbientDataContext;
import vip.liux.front.infrastructure.utils.AmbientScopeProvider;

import java.lang.reflect.Method;
import java.util.function.Supplier;

/**
 * Interceptor for auditing purposes.
 * 审计拦截器
 */
public class AuditingInterceptor implements MethodInterceptor, BeanFactoryAware, Ordered {
    protected final Log logger = LogFactory.getLog(getClass());

    public static final String DEFAULT_AUDITING_STORE_BEAN_NAME = "auditingStore";

    @Nullable
    private BeanFactory beanFactory;

    protected IAuditingManager auditingManager;

    protected IAuditingHelper auditingHelper;

    protected SingletonSupplier<IAuditingStore> defaultAuditingStore;

    protected SingletonSupplier<AmbientScopeProvider<?>> ambientScopeProvider;

    public AuditingInterceptor(IAuditingManager auditingManager) {
        this.auditingManager = auditingManager;
    }

    @Override
    @Nullable
    public Object invoke(@SuppressWarnings("null") final MethodInvocation invocation) {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        final Method userMethod = BridgeMethodResolver.getMostSpecificMethod(invocation.getMethod(), targetClass);
        if (auditingManager.getCurrent() != null) { /* 当前线程时候存在日志记录 */
            // 追加日志记录
            return proceedByLogging(invocation, auditingHelper, auditingManager.getCurrent());
        } else {
            // 开启新的日志记录
            ICurrentUser<?> currentUser = getBeanOfType(ICurrentUser.class);
            return processWithNewAuditingScope(invocation, currentUser, auditingManager, auditingHelper);
        }
    }

    /**
     * 使用新的审计范围处理方法调用
     *
     * @param invocation      the method invocation
     * @param currentUser     the current user
     * @param auditingManager the auditing manager
     * @param auditingHelper  the auditing helper
     * @return the result of the method invocation
     */
    private Object processWithNewAuditingScope(MethodInvocation invocation, ICurrentUser<?> currentUser, IAuditingManager auditingManager, IAuditingHelper auditingHelper) {
        boolean hasError = false;
        try (IAuditLogSaveHandle saveHandle = auditingManager.beginScope()) {
            try {
                Object result = proceedByLogging(invocation, auditingHelper, auditingManager.getCurrent());

                Assert.notNull(auditingManager.getCurrent(), "Auditing scope must not be null");

                if (!auditingManager.getCurrent().log().getExceptions().isEmpty()) {
                    hasError = true;
                }
                return result;
            } catch (Exception e) {
                hasError = true;
                throw e;
            } finally {
                // 确保当前线程是在事务中
                if (TransactionSynchronizationManager.isActualTransactionActive()) {
                    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                        @Override
                        public void afterCompletion(int status) {
                            // if (status == STATUS_ROLLED_BACK) {
                            //     // 如果事务回滚，则获取当前事务的异常信息, 并记录到审计日志中
                            //     if (auditingManager.getCurrent().log().getExceptions().contains(e)) {
                            //         auditingManager.getCurrent().log().getExceptions().add(e);
                            //     }
                            // }
                        }
                    });
                }
                saveHandle.save();
            }
        } catch (Exception e) {
            logger.error("Error occurred while saving audit log", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 继续方法调用并记录结果
     *
     * @param invocation     the method invocation
     * @param auditingHelper the auditing helper
     * @param auditLogScope  the current auditing scope
     * @return the result of the method invocation
     */
    private Object proceedByLogging(MethodInvocation invocation, IAuditingHelper auditingHelper, AuditLogScope auditLogScope) {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        AuditLogInfo auditLog = auditLogScope.log();

        AuditLogActionInfo auditLogAction = auditingHelper.createAuditLogAction(auditLog, targetClass, invocation.getMethod(), invocation.getArguments());

        Stopwatch stopwatch = Stopwatch.createUnstarted();

        stopwatch.start();

        try {
            return invocation.proceed();
        } catch (Throwable e) {
            auditLog.getExceptions().add(new RuntimeException(e));
            throw new RuntimeException(e);
        } finally {
            stopwatch.stop();
            if (auditLogAction != null) {
                auditLogAction.setExecutionDuration((int) stopwatch.elapsed().toMillis());
                auditLog.getActions().add(auditLogAction);
            }
        }
    }

    public void configure(Supplier<IAuditingStore> defaultAuditingStore, Supplier<AmbientScopeProvider<?>> ambientScopeProvider) {
        this.defaultAuditingStore = new SingletonSupplier<>(defaultAuditingStore, () -> getAuditingStore(this.beanFactory));
        this.ambientScopeProvider = new SingletonSupplier<>(ambientScopeProvider, () -> new AmbientScopeProvider<>(new AmbientDataContext()));
    }


    protected <T> T getBeanOfType(Class<T> type) {
        if (this.beanFactory != null) {
            return this.beanFactory.getBean(type);
        }
        throw new IllegalStateException("BeanFactory is required");
    }

    @Nullable
    protected IAuditingStore getAuditingStore(@Nullable BeanFactory beanFactory) {
        if (beanFactory != null) {
            try {
                return beanFactory.getBean(IAuditingStore.class);
            } catch (NoUniqueBeanDefinitionException ex) {
                logger.debug("Could not find unique IAuditingStore bean. " +
                        "Continuing search for an IAuditingStore bean named 'auditingStore'", ex);
                try {
                    return beanFactory.getBean(DEFAULT_AUDITING_STORE_BEAN_NAME, IAuditingStore.class);
                } catch (NoSuchBeanDefinitionException ex2) {
                    if (logger.isInfoEnabled()) {
                        logger.info("More than one IAuditingStore bean found within the context, and none is named " +
                                "'auditingStore'. Mark one of them as primary or name it 'auditingStore' (possibly " +
                                "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
                    }
                }
            } catch (NoSuchBeanDefinitionException ex) {
                logger.debug("Could not find default IAuditingStore bean. " +
                        "Continuing search for an Executor bean named 'taskExecutor'", ex);
                try {
                    return beanFactory.getBean(DEFAULT_AUDITING_STORE_BEAN_NAME, IAuditingStore.class);
                } catch (NoSuchBeanDefinitionException ex2) {
                    logger.info("No task executor bean found for async processing: " +
                            "no bean of type TaskExecutor and no bean named 'auditingStore' either");
                }
                // Giving up -> either using local default executor or none at all...
            }
        }
        return null;
    }

    @Override
    public int getOrder() {
        return 1;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setBeanFactory(@NotNull BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        if (this.auditingHelper == null) {
            this.auditingHelper = new AuditingHelper();
        }
        if (this.auditingManager == null) {
            this.auditingManager = new AuditingManager(defaultAuditingStore.get(), this.auditingHelper, (AmbientScopeProvider<AuditLogScope>) ambientScopeProvider.get());
        }
    }
}
